/*******************************************************************************
 * Copyright (c) 2009 Kevin Bierhoff and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Kevin Bierhoff - initial API and implementation
 *******************************************************************************/
package com.googlecode.twelfdevtools.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;

import com.googlecode.twelfdevtools.builder.TwelfBuilder;


abstract class AbstractElfFileModel 
implements IElfFile, IDeclarationContext {
	
	/** Project this file is part of, if known */
	protected final TwelfProjectImpl project;
	/** Underlying file resource, if known */
	protected final IFile file;

	/** Cached model entries for this file */
	private List<ModelEntry> entries;
	/** Delegate for {@link #getConstant(String)} */
	private DeclarationContextImpl wholeFileContext;
	
	public AbstractElfFileModel(TwelfProjectImpl parent, IFile resource) {
		this.project = parent;
		this.file = resource;
	}

	public IDeclarationContext getContext(int fileOffset) {
		final List<ModelEntry> es = getEntries();
		final int start = getClosestIndexBefore(es, fileOffset);
		// create context that starts searching right before found entry
		return new DeclarationContextImpl(start, es);
	}
	
	public ITwelfConstant getConstant(String id) {
		if(wholeFileContext == null) {
			wholeFileContext = new DeclarationContextImpl(getEntries());
		}
		return wholeFileContext.getConstant(id);
	}

	public void collectMatches(
			Map<String, ? super ITwelfConstant> matches, String prefix) {
		if(wholeFileContext == null) {
			wholeFileContext = new DeclarationContextImpl(getEntries());
		}
		wholeFileContext.collectMatches(matches, prefix);
	}

	public ITwelfConstant getDeclaration(int fileOffset) {
		List<ModelEntry> es = getEntries();
		int declIndex = getClosestIndexBefore(es, fileOffset);
		if(declIndex < 0)
			return null;
		ModelEntry decl = es.get(declIndex);
		ITwelfConstant result = decl.getValue();
		IRegion r = result.getDeclarationRange();
		if(fileOffset <= r.getOffset() + r.getLength())
			return result;
		else
			// offset is not on a declaration
			return null;
	}
	
	public List<ITwelfModelElement> getChildren() {
		return getChildren(this);
	}
	
	private List<ITwelfModelElement> getChildren(ITwelfModelElement parent) {
		List<ModelEntry> es = getEntries();
		ArrayList<ITwelfModelElement> result = new ArrayList<ITwelfModelElement>(es.size());
		for(ModelEntry e : es) {
			if (e.getValue().getParent() == parent)
				result.add(e.getValue());
		}
		return result;
	}
	
	private static int getClosestIndexBefore(final List<ModelEntry> es, int fileOffset) {
		// find entry right before fileOffset
		int i;
		for(i = 0; i < es.size(); i++) {
			if(fileOffset < es.get(i).getKey())
				return i-1;
		}
		
		return es.size() - 1;
	}
	
	private List<ModelEntry> getEntries() {
		if(entries == null) {
			try {
				List<TwelfConstantImpl> constants = getAllMarkers(TwelfBuilder.TYPING_MARKER_TYPE, true);
				
				entries = new ArrayList<ModelEntry>(constants.size());
				
				for(TwelfConstantImpl c : constants) {
					int start = c.getIdentifierRange().getOffset();
					if(start < 0)
						// weird: type marker without start position
						continue;
					
					entries.add(new ModelEntry(start, c));
				}
			} 
			catch (CoreException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			Collections.sort(entries);
			
			// find "cases" and use the constant they define as their parent
			// do this after sorting so we correctly handle shadowing
			// this can re-order the outline compared to the file if other stuff 
			// is declared between a constant and one of its cases
			Map<String, ITwelfConstant> parents = new HashMap<String, ITwelfConstant>();
			for (ModelEntry e : entries) {
				String d = e.c.getDeclaration().trim();
				if (d.endsWith("type")) {
					// judgment, put it in the map
					parents.put(e.c.getId(), e.c);
					continue;
				}
				int pos = d.lastIndexOf("->");
				if (pos < 0)
					pos = d.lastIndexOf(':');
				if (pos >= 0) {
					// TODO speed this up by "finding" the judgment in finalDef and looking it up
					for (Map.Entry<String, ITwelfConstant> p : parents.entrySet()) {
						// this is pretty bootlegged: prepend space so we skip any {X:...} stuff
						if (d.indexOf(" " + p.getKey(), pos + 1) > 0)
							e.setParent(p.getValue());
					}
				}
			}
			
		}
		return entries;
	}

	private IDeclarationContext getPredecessorFileContext() {
		if (project == null)
			return null;
		return getPredecessor();
	}

	protected abstract List<TwelfConstantImpl> getAllMarkers(String markerType, boolean includeSubtypes) throws CoreException;
	/**
	 * Pass is the position of the first and last character (not the character after the last one).
	 * @param m
	 * @param start identifier beginning offset (inclusive)
	 * @param end <i>inclusive</i> identifier end offset
	 * @return
	 */
	protected final TwelfConstantImpl createConstantModel(IMarker m, final Position p) {
		final String id = m.getAttribute(TwelfBuilder.ID_ATTRIBUTE, "unknown");
		final String decl = m.getAttribute(IMarker.MESSAGE, "unknown");
		final int declEnd = m.getAttribute(TwelfBuilder.CHAR_DECL_END, -1);
		return new TwelfConstantImpl(p, decl, id, declEnd);
	}
	
	public AbstractElfFileModel getPredecessor() {
		if (project == null)
			throw new UnsupportedOperationException("predecessor unknown");
		return project.getPredecessor(this);
	}

	public ITwelfProject getParent() {
		if (project == null)
			throw new UnsupportedOperationException("predecessor unknown");
		return project;
	}

	public IFile getResource() {
		if (file == null)
			throw new UnsupportedOperationException("resource unknown");
		return file;
	}

	protected final class TwelfConstantImpl implements ITwelfConstant {
		
		/** position is mutable and can change when document is edited */
		private final Position p;
		private final String decl;
		private final String id;
		// TODO track declEnd as Position in annotation model, similar to p
		private final int declEnd;
		private ITwelfModelElement parent;

		private TwelfConstantImpl(Position p, String decl, String id,
				int declEnd) {
			this.p = p;
			this.decl = decl;
			this.id = id;
			this.declEnd = declEnd;
			this.parent = getFile();
		}

		public String getDeclaration() {
			return decl;
		}

		public String getId() {
			return id;
		}

		public IRegion getDeclarationRange() {
			// TODO declEnd adapt to input changes (maybe with Position?)
			// declEnd is the last character of the declaration, so need to add 1
			final IRegion range = new Region(p.offset, declEnd - p.offset + 1);
			return range;
		}

		public IRegion getIdentifierRange(IDocument doc) {
			return getIdentifierRange();
		}

		public IRegion getIdentifierRange() {
			// positions are a little weird--length is always 1 too short
			final IRegion idRange = new Region(p.offset, p.length + 1);
			// TODO cache idRange
			return idRange;
		}

		public ITwelfModelElement getParent() {
			// TODO maybe we reflect dependency hierarchy in the future
			return parent;
		}

		public IElfFile getFile() {
			return AbstractElfFileModel.this; 
		}

		public List<? extends ITwelfModelElement> getChildren() {
			return AbstractElfFileModel.this.getChildren(this);
		}
	}

	private final class DeclarationContextImpl implements IDeclarationContext {
		private final int start;
		private final List<ModelEntry> es;
		
		private DeclarationContextImpl(List<ModelEntry> es) {
			// start with last entry
			this.start = es.size() - 1;
			this.es = es;
		}

		private DeclarationContextImpl(int start, List<ModelEntry> es) {
			this.start = start;
			this.es = es;
		}

		public ITwelfConstant getConstant(String id) {
			// search backwards from start position
			for(int j = start; j >= 0; j--) {
				ITwelfConstant cur = es.get(j).getValue();
				if(cur.getId().equals(id))
					return cur;
			}
			IDeclarationContext predFile = 
				AbstractElfFileModel.this.getPredecessorFileContext();
			if (predFile != null)
				return predFile.getConstant(id);
			// else no predecessor
			return null;
		}

		public void collectMatches(
				Map<String, ? super ITwelfConstant> matches, String prefix) {
			for(int j = start; j >= 0; j--) {
				ITwelfConstant cur = es.get(j).getValue();
				if(cur.getId().startsWith(prefix) && ! matches.containsKey(cur.getId()))
					// contains assures we put the "latest" constant of the given name in
					matches.put(cur.getId(), cur);
			}
			IDeclarationContext predFile = 
				AbstractElfFileModel.this.getPredecessorFileContext();
			if (predFile != null)
				predFile.collectMatches(matches, prefix);
			// else no predecessor
		}
	}

	private class ModelEntry implements Map.Entry<Integer, ITwelfConstant>,
	Comparable<ModelEntry> {
		
		private int key;
		private ITwelfConstant c;
		
		private ModelEntry(int key, TwelfConstantImpl c) {
			this.key = key;
			this.c = c;
		}
		
		private void setParent(ITwelfConstant parent) {
			if (c instanceof TwelfConstantImpl) {
				((TwelfConstantImpl) c).parent  = parent;
			}
		}

		public Integer getKey() {
			return key;
		}

		public ITwelfConstant getValue() {
			return c;
		}

		public ITwelfConstant setValue(
				ITwelfConstant value) {
			ITwelfConstant result = c;
			c = value;
			return result;
		}

		public int compareTo(ModelEntry arg0) {
			return this.key - arg0.key;
		}
		
		@Override
		public String toString() {
			return "<" + key + "," + c + ">";
		}
		
	}

}
